package com.example.func.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * 工具类：计算当前 Maven 项目的依赖树，并以 JSON 字符串形式返回。
 * 特点：
 * - 调用 Maven Dependency Plugin 输出 JSON 格式依赖树；
 * - 将插件 JSON 中的 children 字段转换为期望的 dependencies 字段；
 * - 保留 groupId、artifactId、version、scope 四个关键字段；
 * - 兼容单模块与多模块工程（多模块会返回多个根节点）。
 */
@Slf4j
public class MavenDependencyCounter {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 生成依赖树（基于当前工作目录下的 Maven 项目）。
     * 返回一个 JSON 数组字符串（字符串内容是一个 JSON 数组），
     * 每个元素代表一个根模块，内部以 "dependencies" 作为子依赖列表。
     * 调用处可直接放入键 "dependencyTree" 对应的值。
     */
    public static String buildDependencyTreeJson() {
        try {
            Path start = Paths.get("").toAbsolutePath();
            Path projectDir = findMavenProjectRoot(start);
            return buildDependencyTreeJson(projectDir);
        } catch (Exception e) {
            System.err.println("[MavenDependencyCounter] 构建依赖树失败：" + e.getMessage());
            return "[]";
        }
    }

    /**
     * 从指定 Maven 工程目录生成依赖树 JSON（更适合对外调用的重载）。
     */
    public static String buildDependencyTreeJson(Path projectDir) {
        try {
            // 若传入的是子目录，向上找到包含 pom.xml 的根
            Path mavenRoot = findMavenProjectRoot(projectDir.toAbsolutePath());
            Path outputJson = ensureTargetFile(mavenRoot.resolve("target").resolve("dependency-tree.json"));
            System.out.println(mavenRoot+":"+outputJson);
            log.info("mavenRoot:{},outputJson:{}",mavenRoot,outputJson);
            runMavenDependencyTree(mavenRoot, outputJson);

            JsonNode raw = readJson(outputJson);
            ArrayNode normalized = normalizeDependencyTree(raw);

            if (normalized.size() == 1 && normalized.get(0).path("dependencies").isArray()
                    && normalized.get(0).path("dependencies").size() == 0) {
                String stdout = fetchTextTreeViaStdout(mavenRoot);
                ArrayNode textParsed = parseTextTreeToArrayNode(stdout);
                if (textParsed.size() > 0) {
                    return MAPPER.writeValueAsString(textParsed);
                }
            }

            return MAPPER.writeValueAsString(normalized);
        } catch (Exception e) {
            System.err.println("[MavenDependencyCounter] 构建依赖树失败：" + e.getMessage());
            return "[]";
        }
    }

    /**
     * 简单命令行测试入口：打印依赖树 JSON 数组字符串。
     */
    public static void main(String[] args) {
        String dependencyTree = buildDependencyTreeJson();
        System.out.println(dependencyTree);
    }

    /**
     * 确保输出文件存在（若已存在则清空内容）。
     */
    private static Path ensureTargetFile(Path file) throws IOException {
        Files.createDirectories(file.getParent());
        if (!Files.exists(file)) {
            Files.createFile(file);
        } else {
            // 清空旧内容，避免不同次输出拼接
            Files.write(file, new byte[0]);
        }
        return file;
    }

    /**
     * 通过 Maven Dependency Plugin 生成 JSON 依赖树到文件。
     * 使用命令：
     * mvn -q org.apache.maven.plugins:maven-dependency-plugin:3.6.1:tree 
     *     -DoutputType=json -DappendOutput=false -DoutputFile=...
     * - Windows 使用 mvn.cmd，类 Unix 使用 mvn；
     * - 设置超时时间，防止构建挂起；
     * - 合并标准错误到标准输出并消费，防止阻塞。
     */
    private static void runMavenDependencyTree(Path projectDir, Path outputJson) throws IOException, InterruptedException {
        // Use maven-dependency-plugin to emit JSON. Works without adding the plugin to the POM.
        // We request JSON output to a file to avoid parsing stdout noise.
        // Command: mvn -q org.apache.maven.plugins:maven-dependency-plugin:3.6.1:tree -DoutputType=json -DoutputFile=... 
        String mvnCmd = isWindows() ? "mvn.cmd" : "mvn";
        List<String> command = new ArrayList<>();
        command.add(mvnCmd);
        // 保持默认日志级别，避免 -q 误隐藏部分树输出
        command.add("org.apache.maven.plugins:maven-dependency-plugin:3.6.1:tree");
        command.add("-DoutputType=json");
        command.add("-DappendOutput=false");
        command.add("-DoutputFile=" + outputJson.toAbsolutePath());

        ProcessBuilder pb = new ProcessBuilder(command);
        pb.directory(projectDir.toFile());
        pb.redirectErrorStream(true);
        Process p = pb.start();

        // 消费进程输出，避免缓冲区满导致子进程阻塞
        try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            while (r.readLine() != null) {
                // no-op
            }
        }

        // 等待进程结束（含超时保护）
        boolean finished = p.waitFor(180, TimeUnit.SECONDS);
        if (!finished || p.exitValue() != 0) {
            throw new IOException("Failed to run mvn dependency:tree, exit=" + (finished ? p.exitValue() : "timeout") +
                    ", dir=" + projectDir.toAbsolutePath());
        }

        // 校验输出文件是否生成且非空
        try {
            long size = Files.size(outputJson);
            if (size == 0L) {
                throw new IOException("dependency-tree.json 为空，可能是 Maven 未安装/不可用，或插件未成功执行。目录：" + projectDir);
            }
        } catch (IOException e) {
            throw new IOException("读取输出文件失败：" + outputJson + ", " + e.getMessage(), e);
        }
    }

    /**
     * 捕获 mvn dependency:tree 的标准输出文本（ASCII 树），用于兜底解析传递依赖。
     */
    private static String fetchTextTreeViaStdout(Path projectDir) throws IOException, InterruptedException {
        String mvnCmd = isWindows() ? "mvn.cmd" : "mvn";
        List<String> command = new ArrayList<>();
        command.add(mvnCmd);
        // 不使用 -q，尽量保留树输出
        command.add("org.apache.maven.plugins:maven-dependency-plugin:3.6.1:tree");
        command.add("-Dverbose"); // 尽量包含完整的传递依赖
        // 不指定 outputFile，直接读 stdout

        ProcessBuilder pb = new ProcessBuilder(command);
        pb.directory(projectDir.toFile());
        pb.redirectErrorStream(true);
        Process p = pb.start();

        StringBuilder out = new StringBuilder(4096);
        try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            String line;
            while ((line = r.readLine()) != null) {
                out.append(line).append('\n');
            }
        }

        boolean finished = p.waitFor(180, TimeUnit.SECONDS);
        if (!finished || p.exitValue() != 0) {
            throw new IOException("Failed to run mvn dependency:tree (stdout), exit=" + (finished ? p.exitValue() : "timeout"));
        }
        return out.toString();
    }

    /**
     * 判断是否 Windows 系统，用于选择 mvn 可执行名称。
     */
    private static boolean isWindows() {
        return System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).contains("win");
    }

    /**
     * 读取 JSON 文件：
     * - 兼容多模块场景：有些版本会按行输出多个模块的 JSON；
     * - 若是单个对象，则包装成数组；
     * - 若本身是数组则直接返回。
     */
    private static JsonNode readJson(Path outputJson) throws IOException {
        // Some Maven versions may output multiple JSON objects (for multi-module) separated by newlines.
        // We will try to parse as an array; if not, wrap single object in an array.
        String content = new String(Files.readAllBytes(outputJson), StandardCharsets.UTF_8).trim();
        if (content.isEmpty()) {
            throw new IOException("输出文件为空：" + outputJson.toAbsolutePath());
        }
        // If file contains multiple top-level JSON objects separated by newlines, split and wrap.
        List<JsonNode> modules = new ArrayList<>();
        for (String line : content.split("\r?\n")) {
            String s = line.trim();
            if (s.isEmpty()) continue;
            try {
                modules.add(MAPPER.readTree(s));
            } catch (JsonProcessingException ignore) {
                // Not a single-line JSON; fall back to parse the whole file once
                modules.clear();
                break;
            }
        }
        if (!modules.isEmpty()) {
            ArrayNode arr = MAPPER.createArrayNode();
            arr.addAll(modules);
            return arr;
        }
        try {
            JsonNode node = MAPPER.readTree(content);
            if (node.isArray()) return node;
            ArrayNode arr = MAPPER.createArrayNode();
            arr.add(node);
            return arr;
        } catch (JsonProcessingException notJson) {
            // 回退：解析 Maven 经典文本树格式
            ArrayNode arr = parseTextTreeToArrayNode(content);
            if (arr.size() > 0) return arr;
            throw notJson;
        }
    }

    /**
     * 解析 Maven 经典文本依赖树为目标 JSON 数组（含传递依赖）。
     */
    private static ArrayNode parseTextTreeToArrayNode(String content) {
        // 预处理文本：去除 [INFO] 前缀、裁剪噪声
        String[] lines = preprocessTextTree(content);
        ArrayNode roots = MAPPER.createArrayNode();
        if (lines.length == 0) return roots; 

        List<ObjectNode> nodeStack = new ArrayList<>();
        List<Integer> depthStack = new ArrayList<>();

        for (String raw : lines) {
            String line = raw.replace('\t', ' ').replace("\u00A0", " ");
            String trimmed = line.trim();
            if (trimmed.isEmpty()) continue;

            // 优先识别分支行（包含 +- 或 \\- 标记）
            int i = 0, prefixDepth = 0;
            boolean isBranch = false;
            while (i < line.length()) {
                if (i + 3 <= line.length() && (line.startsWith("|  ", i) || line.startsWith("   ", i))) {
                    prefixDepth++;
                    i += 3;
                    continue;
                }
                if (i + 3 <= line.length() && (line.startsWith("+- ", i) || line.startsWith("\\- ", i))) {
                    isBranch = true;
                    i += 3; // 跳过标记，i 指向坐标起始
                    break;
                }
                if (Character.isWhitespace(line.charAt(i)) || line.charAt(i) == '|' || line.charAt(i) == '+'
                        || line.charAt(i) == '-' || line.charAt(i) == '\\') {
                    i++;
                    continue;
                }
                break;
            }

            if (isBranch) {
                String coord = line.substring(i).trim();
                ObjectNode node = parseGAV(coord);
                if (node == null) continue;

                // 分支行的逻辑深度 = 前缀深度 + 1（根为 0，第一级子依赖为 1）
                int depth = prefixDepth + 1;
                // 对齐深度（父级深度为 depth-1）
                while (!depthStack.isEmpty() && depthStack.get(depthStack.size() - 1) >= depth) {
                    depthStack.remove(depthStack.size() - 1);
                    nodeStack.remove(nodeStack.size() - 1);
                }
                if (nodeStack.isEmpty()) {
                    // 若无根（异常保护），作为根加入
                    node.putArray("dependencies");
                    roots.add(node);
                    nodeStack.add(node);
                    depthStack.add(depth);
                } else {
                    ObjectNode parent = nodeStack.get(nodeStack.size() - 1);
                    ArrayNode deps = (ArrayNode) parent.withArray("dependencies");
                    node.putArray("dependencies");
                    deps.add(node);
                    nodeStack.add(node);
                    depthStack.add(depth);
                }
                continue;
            }

            // 非分支行：视为模块根坐标
            ObjectNode root = parseGAV(trimmed);
            if (root != null) {
                root.putArray("dependencies");
                roots.add(root);
                nodeStack.clear();
                depthStack.clear();
                nodeStack.add(root);
                depthStack.add(0);
            }
        }

        return roots;
    }

    // 去除 [INFO] 前缀、(optional) 等附加说明，只保留依赖树相关行
    private static String[] preprocessTextTree(String content) {
        List<String> kept = new ArrayList<>();
        String[] rawLines = content.split("\r?\n");
        boolean seenRoot = false;
        for (String l : rawLines) {
            String line = l;
            // 去掉 Maven 日志前缀
            if (line.startsWith("[INFO] ")) {
                line = line.substring(7);
            }
            String t = line.trim();
            if (t.isEmpty()) continue;

            // 去除注释性括号尾注，如 (optional), (version managed from ...)
            int idxNote = t.indexOf(" (");
            if (idxNote > 0) {
                t = t.substring(0, idxNote).trim();
            }

            // 过滤掉明显与依赖无关的日志行
            String lower = t.toLowerCase(Locale.ENGLISH);
            if (lower.startsWith("finished at") || lower.startsWith("total time") || lower.startsWith("build ")
                    || lower.startsWith("reactor summary") || lower.startsWith("downloading") || lower.startsWith("downloaded")
                    || lower.startsWith("--- maven-dependency-plugin")) {
                continue;
            }

            boolean looksLikeCoord = isLikelyCoordinate(t);
            boolean looksLikeBranch = line.contains("+- ") || line.contains("\\- ");

            if (!seenRoot && looksLikeCoord && !looksLikeBranch) {
                kept.add(t); // 根坐标
                seenRoot = true;
                continue;
            }

            // 根之后仅保留带分支标记的行（更可靠）
            if (seenRoot && looksLikeBranch) {
                kept.add(line);
            }
        }
        return kept.toArray(new String[0]);
    }

    // 简单判断是否为坐标行 g:a:type:version[:scope]
    private static boolean isLikelyCoordinate(String s) {
        String[] parts = s.split(":");
        if (parts.length < 4) return false;
        // groupId 与 artifactId 必须是典型坐标格式
        if (!parts[0].matches("[A-Za-z0-9_.-]+")) return false;
        if (!parts[1].matches("[A-Za-z0-9_.-]+")) return false;
        // version 不包含空白
        if (parts[3].trim().isEmpty() || parts[3].contains(" ")) return false;
        return true;
    }

    // 解析 groupId:artifactId:type:version[:scope]
    private static ObjectNode parseGAV(String coord) {
        String s = coord.trim();
        if (s.isEmpty()) return null;
        // 去除尾部的注释性括号
        int idx = s.indexOf(" (");
        if (idx > 0) s = s.substring(0, idx).trim();

        String[] parts = s.split(":");
        if (parts.length < 4) return null;
        ObjectNode node = MAPPER.createObjectNode();
        node.put("groupId", parts[0]);
        node.put("artifactId", parts[1]);
        node.put("version", parts[3]);
        if (parts.length >= 5) {
            node.put("scope", parts[4]);
        }
        node.putArray("dependencies");
        return node;
    }

    /**
     * 自底向上查找包含 pom.xml 的目录，作为 Maven 工程根目录。
     */
    private static Path findMavenProjectRoot(Path start) throws IOException {
        Path cur = start;
        while (cur != null) {
            if (Files.exists(cur.resolve("pom.xml"))) {
                return cur;
            }
            Path parent = cur.getParent();
            if (parent == null || parent.equals(cur)) {
                break;
            }
            cur = parent;
        }
        throw new IOException("未找到 pom.xml，请在 Maven 工程目录或其子目录中运行。起始目录：" + start);
    }

    /**
     * 规范化插件输出为目标结构：
     * - 将 children 递归转换为 dependencies；
     * - 仅复制关键信息：groupId、artifactId、version、scope。
     */
    private static ArrayNode normalizeDependencyTree(JsonNode rawModules) {
        ArrayNode result = MAPPER.createArrayNode();
        if (rawModules == null || !rawModules.isArray()) {
            return result;
        }
        for (JsonNode module : rawModules) {
            // The dependency plugin JSON typically uses keys: artifactId, groupId, version, packaging/type, scope, children
            ObjectNode root = MAPPER.createObjectNode();
            copyIfPresent(module, root, "groupId");
            copyIfPresent(module, root, "artifactId");
            copyIfPresent(module, root, "version");
            copyIfPresent(module, root, "scope");
            ArrayNode deps = transformChildren(module.path("children"));
            root.set("dependencies", deps);
            result.add(root);
        }
        return result;
    }

    /**
     * 递归转换子节点：children -> dependencies。
     */
    private static ArrayNode transformChildren(JsonNode children) {
        ArrayNode arr = MAPPER.createArrayNode();
        if (children == null || !children.isArray()) {
            return arr;
        }
        for (JsonNode child : children) {
            ObjectNode node = MAPPER.createObjectNode();
            copyIfPresent(child, node, "groupId");
            copyIfPresent(child, node, "artifactId");
            copyIfPresent(child, node, "version");
            copyIfPresent(child, node, "scope");
            ArrayNode grand = transformChildren(child.path("children"));
            if (grand.size() > 0) {
                node.set("dependencies", grand);
            } else {
                node.putArray("dependencies");
            }
            arr.add(node);
        }
        return arr;
    }

    /**
     * 若源节点存在对应字段，则复制到目标对象。
     */
    private static void copyIfPresent(JsonNode src, ObjectNode dst, String field) {
        JsonNode v = src.get(field);
        if (v != null && !v.isNull()) {
            dst.set(field, v);
        }
    }
}
